package com.soccerlover.strictfield.advisor;

import com.soccerlover.strictfield.annotation.CheckTarget;
import com.soccerlover.strictfield.annotation.ExcludeStrictCheck;
import com.soccerlover.strictfield.annotation.NULL;
import com.soccerlover.strictfield.annotation.StrictField;
import com.soccerlover.strictfield.util.ReflectUtil;
import org.aopalliance.aop.Advice;
import org.springframework.aop.ClassFilter;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.support.StaticMethodMatcherPointcutAdvisor;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;

/**
 * @author hck soccerlover9527@gmail.com
 */
public class StrictFieldAdvice extends StaticMethodMatcherPointcutAdvisor implements BeanPostProcessor {
    /**
     * 校验缓存
     * key   -> {@link Method#toGenericString()}
     * val   -> 校验字段相关的元数据.{@link StrictFieldMeta}
     */
    private final Map<String, List<StrictFieldMeta>> STRICT_CACHE = new ConcurrentHashMap<>();

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        //  strict field 只会对 Controller 参数进行校验
        if (!bean.getClass().isAnnotationPresent(Controller.class) && !bean.getClass().isAnnotationPresent(RestController.class)) {
            return bean;
        }

        Method[] methods = bean.getClass().getMethods();
        for (Method method : methods) {
            //  只对 controller 的请求做参数校验
            if (!(method.isAnnotationPresent(GetMapping.class) || method.isAnnotationPresent(RequestMapping.class)
                    || method.isAnnotationPresent(PostMapping.class) || method.isAnnotationPresent(PutMapping.class)
                    || method.isAnnotationPresent(DeleteMapping.class) || method.isAnnotationPresent(PatchMapping.class))) {
                continue;
            }
            Parameter[] parameters = method.getParameters();
            if (parameters.length == 0) {
                continue;
            }
            List<StrictFieldMeta> list = new ArrayList<>();
            for (int paramIndex = 0; paramIndex < parameters.length; paramIndex++) {
                Parameter parameter = parameters[paramIndex];
                Class<?> parameterType = parameter.getType();
                // 基本类型
                if (parameterType.isMemberClass() && parameterType.isAnnotationPresent(StrictField.class)) {
                    StrictField annotation = parameter.getAnnotation(StrictField.class);
                    CheckTarget targetAnnotation = annotation.value();
                    Set<Object> strictValues = new HashSet<>();
                    extractBasicParam(parameterType, targetAnnotation, strictValues);
                    if (strictValues.isEmpty()) {
                        continue;
                    }
                    list.add(new StrictFieldMeta(true, paramIndex, null, strictValues));
                } else {
                    //  复杂对象作为入参,如下,ParamDTO包含需要校验的字段
                    //  handleRequest(ParamDTO param);
                    Field[] declaredFields = parameter.getType().getDeclaredFields();
                    for (Field checkField : declaredFields) {
                        if (checkField.isAnnotationPresent(StrictField.class)) {
                            StrictField annotation = checkField.getAnnotation(StrictField.class);
                            Class<?> type = checkField.getType();
                            if (!String.class.equals(type) && !type.isMemberClass()) {
                                /*
                                 *  需要校验的字段只支持基本类型
                                 *  ParamDTO {
                                 *    @StrictField   支持基本数据类型
                                 *    private String orderBy;
                                 *    @StrictField   不支持.后续考虑支持枚举对象
                                 *    private AnotherClass obj;
                                 * }
                                 */
                                continue;
                            }
                            CheckTarget targetAnnotation = annotation.value();
                            Set<Object> strictValues = new HashSet<>();
                            if (type.equals(String.class)) {
                                //  处理 strictField 为 string 类型的情况
                                handleStringField(targetAnnotation, strictValues);
                            } else {
                                //  处理其他基本数据类型
                                extractBasicParam(type, targetAnnotation, strictValues);
                            }
                            if (strictValues.isEmpty()) {
                                continue;
                            }
                            checkField.setAccessible(true);
                            list.add(new StrictFieldMeta(false, paramIndex, checkField, strictValues));
                        }
                    }
                }
            }
            if (!list.isEmpty()) {
                STRICT_CACHE.put(method.toGenericString(), list);
            }
        }
        return bean;
    }

    private void extractBasicParam(Class<?> parameterType, CheckTarget targetAnnotation, Set<Object> strictValues) {
        if (parameterType.equals(byte.class) || parameterType.equals(Byte.class)) {
            byte[] bytesValues = targetAnnotation.byteValuesList();
            Collections.addAll(strictValues, bytesValues);
        } else if (parameterType.equals(int.class) || parameterType.equals(Integer.class)) {
            int[] intsValues = targetAnnotation.intValuesList();
            Collections.addAll(strictValues, intsValues);
        } else if (parameterType.equals(long.class) || parameterType.equals(Long.class)) {
            long[] longsValues = targetAnnotation.longValuesList();
            Collections.addAll(strictValues, longsValues);
        }
    }

    private void handleStringField(CheckTarget targetAnnotation, Set<Object> strictValues) {
        //  需要匹配的目标类
        Class<?> targetClass = targetAnnotation.value();
        //  对于 string 类型的字段,可以提取的校验值有两个. CheckTarget.value 和 CheckTarget.stringValuesList
        if (!targetClass.equals(NULL.class)) {
            boolean supperClassValue = targetAnnotation.supperClassValue();
            String[] exclude = targetAnnotation.exclude();
            Set<String> excludeSet = Arrays.stream(exclude).collect(Collectors.toSet());
            //  获取指定类的对应字段
            List<Field> targetObjFields = ReflectUtil.getAllFields(targetClass, supperClassValue);

            for (Field targetObjField : targetObjFields) {
                if (targetObjField.isAnnotationPresent(ExcludeStrictCheck.class)
                        || excludeSet.contains(targetObjField.getName())) {
                    //  被 ExcludeStrictCheck 标记或者被包含在 exclude 集合中，则跳过
                    continue;
                }
                strictValues.add(targetObjField.getName());
            }
        }
        String[] strings = targetAnnotation.stringValuesList();
        //  额外添加校验值. 复杂类型属性值和字符串匹配可以同时使用
        if (strings.length > 0) {
            //  添加字符串匹配的校验值
            Collections.addAll(strictValues, strings);
        }
    }

    @Override
    public void setClassFilter(@Nullable ClassFilter classFilter) {
        super.setClassFilter(clazz -> clazz.isAnnotationPresent(Controller.class)
                || clazz.isAnnotationPresent(RestController.class)
                || org.springframework.web.servlet.mvc.Controller.class.isAssignableFrom(clazz)
        );
    }

    @Override
    public boolean matches(@NonNull Method method, Class<?> targetClass) {
        return (targetClass.isAnnotationPresent(Controller.class)
                || targetClass.isAnnotationPresent(RestController.class)
                || org.springframework.web.servlet.mvc.Controller.class.isAssignableFrom(targetClass))
                && STRICT_CACHE.containsKey(method.toGenericString());
    }

    @Override
    @NonNull
    public Advice getAdvice() {
        return new StrictFieldCheckAdvice();
    }

    /**
     * 真正的校验切面. 从系统初始化生成的缓存信息里面取到该接口中参数的合法值.
     * 如果参数值不在合法值范围内则抛出 {@link IllegalArgumentException}
     */
    private class StrictFieldCheckAdvice implements MethodBeforeAdvice {

        @Override
        public void before(@NonNull Method method, @Nullable Object[] args, Object target) throws Throwable {
            if (Objects.isNull(args)) {
                return;
            }
            List<StrictFieldMeta> strictFieldMetas = STRICT_CACHE.get(method.toGenericString());
            //  校验字段
            for (StrictFieldMeta strictFieldMeta : strictFieldMetas) {
                Object param = args[strictFieldMeta.index];
                Set<?> legalValues = strictFieldMeta.getLegalValues();
                Field field = strictFieldMeta.getField();
                Object checkValue = field.get(param);
                if (Objects.nonNull(checkValue) && !legalValues.contains(checkValue)) {
                    throw new IllegalArgumentException("非法参数");
                }
            }
        }
    }

    /**
     * 元数据，保存需要校验的接口上面的参数相关数据。
     */
    private static class StrictFieldMeta {
        /**
         * 是否是基本数据类型
         */
        private boolean basicType;
        /**
         * 该字段在接口参数的位置
         */
        private int index;
        /**
         * 接口上复杂参数对象需要校验的字段
         */
        private Field field;
        /**
         * 标记的字段值必须在此集合内
         */
        private Set<?> legalValues;

        public StrictFieldMeta(boolean basicType, int index, Field field, Set<?> legalValues) {
            this.basicType = basicType;
            this.index = index;
            this.field = field;
            this.legalValues = legalValues;
        }

        public boolean isBasicType() {
            return basicType;
        }

        public void setBasicType(boolean basicType) {
            this.basicType = basicType;
        }

        public int getIndex() {
            return index;
        }

        public void setIndex(int index) {
            this.index = index;
        }

        public Field getField() {
            return field;
        }

        public void setField(Field field) {
            this.field = field;
        }

        public Set<?> getLegalValues() {
            return legalValues;
        }

        public void setLegalValues(Set<?> legalValues) {
            this.legalValues = legalValues;
        }
    }
}
